package com.basker.pisces.domain.entity.meta;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Stream;

import org.springframework.util.StringUtils;

import com.basker.pisces.domain.command.annotation.CommandMeta;
import com.basker.pisces.domain.command.meta.Command;
import com.basker.pisces.domain.property.annotation.PropertyMeta;
import com.basker.pisces.domain.property.meta.Property;
import com.basker.pisces.domain.validation.annotation.ValidationMeta;
import com.basker.pisces.domain.validation.meta.Validation;
import com.basker.pisces.exception.ExceptionFactory;
import com.basker.pisces.utils.ReflectionUtils;

/**
 * 负责解析实体的注解元素
 *
 * @author hangwen
 */
class BeanEntityAnnotationResolver {

    private Entity entity;

    BeanEntityAnnotationResolver(Entity entity) {
        this.entity = entity;
    }

    List<Command> createCommands() {
        Predicate<Method> matcher = m -> m.isAnnotationPresent(CommandMeta.class);
        Method[] methods = Stream.of(org.springframework.util.ReflectionUtils.getAllDeclaredMethods(entity.getClass()))
                .filter(matcher).toArray(l -> new Method[l]);

        List<Command> commands = new ArrayList<>(methods.length);

        for (Method method : methods) {
            Object ret = ReflectionUtils.invokeMethod(method, entity);

            if (ret == null) {
                continue;
            }

            if (ret instanceof Command) {
                commands.add((Command) ret);
            } else {
                throw ExceptionFactory.createRuntimeException(
                        "pisces-domain.BeanEntityAnnotationResolver.createCommands-001",
                        "Method:{0} return object is not a Command!", method.getName());
            }
        }

        return commands;
    }

    List<Property<?>> createProperties(AbstractEntity hostEntity) {
        Predicate<Method> matcher = m -> {
            if (m.isAnnotationPresent(PropertyMeta.class)) {
                String hostEntityName = m.getAnnotation(PropertyMeta.class).hostEntity();
                return hostEntityName.equals(hostEntity.getName())
                        || (hostEntity.equals(this.entity) && StringUtils.isEmpty(hostEntityName));
            } else {
                return false;
            }
        };

        Method[] methods = Stream.of(org.springframework.util.ReflectionUtils.getAllDeclaredMethods(entity.getClass()))
                .filter(matcher).toArray(l -> new Method[l]);

        List<Property<?>> properties = new ArrayList<>(methods.length);

        for (Method method : methods) {
            Object ret = ReflectionUtils.invokeMethod(method, entity, hostEntity);

            if (ret == null) {
                continue;
            }

            if (ret instanceof Property) {
                properties.add((Property<?>) ret);
            } else {
                throw ExceptionFactory.createRuntimeException(
                        "pisces-domain.BeanEntityAnnotationResolver.createProperties-001",
                        "Method:{0} return object is not a Property!", method.getName());
            }
        }

        return properties;
    }

    List<Validation> createValidations() {
        Predicate<Method> matcher = m -> m.isAnnotationPresent(ValidationMeta.class);
        Method[] methods = Stream.of(org.springframework.util.ReflectionUtils.getAllDeclaredMethods(entity.getClass()))
                .filter(matcher).toArray(l -> new Method[l]);

        List<Validation> validations = new ArrayList<>(methods.length);

        for (Method method : methods) {
            Object ret = ReflectionUtils.invokeMethod(method, entity);

            if (ret == null) {
                continue;
            }

            if (ret instanceof Validation) {
                validations.add((Validation) ret);
            } else {
                throw ExceptionFactory.createRuntimeException(
                        "pisces-domain.BeanEntityAnnotationResolver.createValidations-001",
                        "Method:{0} return object is not a Validation!", method.getName());
            }
        }

        return validations;
    }
}
